//   Copyright 2012,2013 Vaughn Vernon
//
//   Licensed under the Apache License, Version 2.0 (the "License");
//   you may not use this file except in compliance with the License.
//   You may obtain a copy of the License at
//
//       http://www.apache.org/licenses/LICENSE-2.0
//
//   Unless required by applicable law or agreed to in writing, software
//   distributed under the License is distributed on an "AS IS" BASIS,
//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//   See the License for the specific language governing permissions and
//   limitations under the License.

package com.saasovation.collaboration.port.adapter.persistence.view;

import com.saasovation.collaboration.domain.model.calendar.*;
import com.saasovation.collaboration.domain.model.tenant.Tenant;
import com.saasovation.common.event.sourcing.DispatchableDomainEvent;
import com.saasovation.common.event.sourcing.EventDispatcher;
import com.saasovation.common.port.adapter.persistence.AbstractProjection;
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;

import java.util.List;
import java.util.Map;

public class MySQLCalendarProjection extends AbstractProjection
        implements EventDispatcher {

    private final NamedParameterJdbcTemplate jdbcTemplate;

    private static final Class<?>[] understoodEventTypes = {
            CalendarCreated.class,
            CalendarDescriptionChanged.class,
            CalendarRenamed.class,
            CalendarShared.class,
            CalendarUnshared.class
    };

    public MySQLCalendarProjection(EventDispatcher aParentEventDispatcher,
                                   NamedParameterJdbcTemplate jdbcTemplate) {
        super();
        this.jdbcTemplate = jdbcTemplate;
        aParentEventDispatcher.registerEventDispatcher(this);
    }

    @Override
    public void dispatch(DispatchableDomainEvent aDispatchableDomainEvent) {
        this.projectWhen(aDispatchableDomainEvent);
    }

    @Override
    public void registerEventDispatcher(EventDispatcher anEventDispatcher) {
        throw new UnsupportedOperationException("Cannot register additional dispatchers.");
    }

    @Override
    public boolean understands(DispatchableDomainEvent aDispatchableDomainEvent) {
        return this.understandsAnyOf(
                aDispatchableDomainEvent.domainEvent().getClass(),
                understoodEventTypes);
    }

    protected void when(CalendarCreated anEvent) {
        String existsSql = "select calendar_id from tbl_vw_calendar " +
                "where tenant_id = :tenantId and calendar_id = :calendarId";
        MapSqlParameterSource existsParams = new MapSqlParameterSource()
                .addValue("tenantId", anEvent.tenant().id())
                .addValue("calendarId", anEvent.calendarId().id());
        List<Map<String, Object>> result = this.jdbcTemplate.queryForList(existsSql, existsParams);
        // idempotent operation
        if (!result.isEmpty()) {
            return;
        }
        String insertSql = "insert into tbl_vw_calendar(" +
                "calendar_id, description, name, owner_email_address, " +
                "owner_identity, owner_name, tenant_id" +
                ") values(" +
                ":calendarId, :description, :name, " +
                ":ownerEmail, :ownerIdentity, :ownerName, " +
                ":tenantId)";
        MapSqlParameterSource params = new MapSqlParameterSource()
                .addValue("calendarId", anEvent.calendarId().id())
                .addValue("description", anEvent.description())
                .addValue("name", anEvent.name())
                .addValue("ownerEmail", anEvent.owner().emailAddress())
                .addValue("ownerIdentity", anEvent.owner().identity())
                .addValue("ownerName", anEvent.owner().name())
                .addValue("tenantId", anEvent.tenant().id());
        this.jdbcTemplate.update(insertSql, params);

        for (CalendarSharer sharer : anEvent.sharedWith()) {
            insertCalendarSharer(anEvent.tenant(), anEvent.calendarId(), sharer);
        }
    }

    protected void when(CalendarDescriptionChanged anEvent) {
        String sql = "update tbl_vw_calendar set description = :description " +
                "where calendar_id = :calendarId";
        MapSqlParameterSource params = new MapSqlParameterSource()
                .addValue("description", anEvent.description())
                .addValue("calendarId", anEvent.calendarId().id());

        this.jdbcTemplate.update(sql, params);
    }

    protected void when(CalendarRenamed anEvent) {
        String sql = "update tbl_vw_calendar set name = :name " +
                "where calendar_id = :calendarId";
        MapSqlParameterSource params = new MapSqlParameterSource()
                .addValue("name", anEvent.name())
                .addValue("calendarId", anEvent.calendarId().id());
        this.jdbcTemplate.update(sql, params);
    }

    protected void when(CalendarShared anEvent) {
        this.insertCalendarSharer(anEvent.tenant(), anEvent.calendarId(), anEvent.calendarSharer());
    }

    protected void when(CalendarUnshared anEvent) {
        String sql = "delete from tbl_vw_calendar_sharer " +
                "where tenant_id = :tenantId " +
                "and calendar_id = :calendarId " +
                "and participant_identity = :participantIdentity";
        MapSqlParameterSource params = new MapSqlParameterSource()
                .addValue("tenantId", anEvent.tenant().id())
                .addValue("calendarId", anEvent.calendarId().id())
                .addValue("participantIdentity",
                        anEvent.calendarSharer().participant().identity());
        this.jdbcTemplate.update(sql, params);
    }

    private void insertCalendarSharer(Tenant aTenant,
                                      CalendarId aCalendarId,
                                      CalendarSharer aCalendarSharer) {
        String existsSql = "select id from tbl_vw_calendar_sharer " +
                "where tenant_id = :tenantId " +
                "and calendar_id = :calendarId " +
                "and participant_identity = :participantIdentity";

        MapSqlParameterSource existsParams = new MapSqlParameterSource()
                .addValue("tenantId", aTenant.id())
                .addValue("calendarId", aCalendarId.id())
                .addValue("participantIdentity",
                        aCalendarSharer.participant().identity());
        List<Map<String, Object>> result = this.jdbcTemplate.queryForList(existsSql, existsParams);
        // idempotent operation
        if (!result.isEmpty()) {
            return;
        }
        String insertSql = "insert into tbl_vw_calendar_sharer(" +
                "id, calendar_id, participant_email_address, " +
                "participant_identity, participant_name, tenant_id" +
                ") values(" +
                ":id, :calendarId, :participantEmail, :participantIdentity, " +
                ":participantName, :tenantId)";
        MapSqlParameterSource insertParams = new MapSqlParameterSource()
                .addValue("id", 0L)
                .addValue("calendarId", aCalendarId.id())
                .addValue("participantEmail", aCalendarSharer.participant().emailAddress())
                .addValue("participantIdentity", aCalendarSharer.participant().identity())
                .addValue("participantName", aCalendarSharer.participant().name())
                .addValue("tenantId", aTenant.id());
        this.jdbcTemplate.update(insertSql, insertParams);
    }
}
